package com.reebake.ideal.security.oauth2.controller;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.func.LambdaUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.reebake.ideal.mybatis.core.PageResult;
import com.reebake.ideal.security.oauth2.entity.RegisteredClientEntity;
import com.reebake.ideal.security.oauth2.service.RegisteredClientService;
import com.reebake.ideal.security.oauth2.vo.RegisteredClientQueryParamVO;
import com.reebake.ideal.security.oauth2.vo.RegisteredClientSaveVO;
import com.reebake.ideal.security.oauth2.vo.RegisteredClientVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.web.bind.annotation.*;

import java.time.Duration;
import java.time.Instant;
import java.util.List;

@RestController
@RequestMapping("/oauth2/registered-client")
public class RegisteredClientController {
    @Autowired
    private RegisteredClientRepository registeredClientRepository;
    @Autowired
    private RegisteredClientService registeredClientService;
    @Autowired
    private PasswordEncoder passwordEncoder;

    @PostMapping("page")
    public PageResult<RegisteredClientVO> page(@RequestBody RegisteredClientQueryParamVO queryParam) {
        LambdaQueryWrapper<RegisteredClientEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(StrUtil.isNotBlank(queryParam.getClientId()), RegisteredClientEntity::getClientId, queryParam.getClientId());
        queryWrapper.like(StrUtil.isNotBlank(queryParam.getClientName()), RegisteredClientEntity::getClientName, queryParam.getClientName());

        queryParam = registeredClientService.page(queryParam, queryWrapper);

        return queryParam.to(RegisteredClientVO.class);
    }

    public RegisteredClient convert(RegisteredClientSaveVO registeredClientVO) {

        RegisteredClient existsOne = null;
        if (StrUtil.isBlank(registeredClientVO.getId())) {
            registeredClientVO.setId(IdUtil.fastSimpleUUID());
        } else {
            existsOne = registeredClientRepository.findById(registeredClientVO.getId());
        }
        RegisteredClient.Builder builder = RegisteredClient.withId(registeredClientVO.getId())
                // 客户端id
                .clientId(registeredClientVO.getClientId())
                .clientName(registeredClientVO.getClientName())
                .clientIdIssuedAt(Instant.now());

        if (StrUtil.isNotBlank(registeredClientVO.getClientSecret())) {
            // 客户端秘钥，使用密码解析器加密
            builder.clientSecret(passwordEncoder.encode(registeredClientVO.getClientSecret()));
        } else {
            if (ObjUtil.isNotNull(existsOne)) {
                builder.clientSecret(existsOne.getClientSecret());
            }
        }
        if (ObjUtil.isNotNull(registeredClientVO.getClientSecretExpiresAt())) {
            builder.clientSecretExpiresAt(Instant.ofEpochMilli(registeredClientVO.getClientSecretExpiresAt().getTime()));
        } else {
            if (ObjUtil.isNotNull(existsOne)) {
                builder.clientSecretExpiresAt(existsOne.getClientSecretExpiresAt());
            }
        }
        // 客户端认证方式，基于请求头的认证
        if (CollectionUtil.isNotEmpty(registeredClientVO.getClientAuthenticationMethods())) {
            for (String clientAuthenticationMethod : registeredClientVO.getClientAuthenticationMethods()) {
                builder.clientAuthenticationMethod(new ClientAuthenticationMethod(clientAuthenticationMethod));
            }
        }
        // 配置资源服务器使用该客户端获取授权时支持的方式
        if (CollectionUtil.isNotEmpty(registeredClientVO.getAuthorizationGrantTypes())) {
            for (String grantType : registeredClientVO.getAuthorizationGrantTypes()) {
                builder.authorizationGrantType(new AuthorizationGrantType(grantType));
            }
        }
        // 授权码模式回调地址，oauth2.1已改为精准匹配，不能只设置域名，并且屏蔽了localhost
        if (StrUtil.isNotBlank(registeredClientVO.getRedirectUris())) {
            for (String redirectUri : registeredClientVO.getRedirectUris().split(",")) {
                builder.redirectUri(redirectUri);
            }
        }
        // 该客户端的授权范围，OPENID与PROFILE是IdToken的scope，获取授权时请求OPENID的scope时认证服务会返回IdToken
        builder
                .scope(OidcScopes.OPENID)
                .scope(OidcScopes.PROFILE);
        if (StrUtil.isNotBlank(registeredClientVO.getScopes())) {
            for (String scope : registeredClientVO.getScopes().split(",")) {
                builder.scope(scope);
            }
        }
        if (StrUtil.isNotBlank(registeredClientVO.getPostLogoutRedirectUris())) {
            for (String postLogoutRedirectUri : registeredClientVO.getPostLogoutRedirectUris().split(",")) {
                builder.postLogoutRedirectUri(postLogoutRedirectUri);
            }
        }

        builder.clientSettings(ClientSettings.builder()
                        // 客户端设置，设置用户需要确认授权
                        .requireAuthorizationConsent(registeredClientVO.getRequireAuthorizationConsent())
                        .setting(LambdaUtil.getFieldName(RegisteredClient::getClientSecret), Base64.encode(registeredClientVO.getClientSecret())).build())
                .tokenSettings(TokenSettings.builder()
                        .accessTokenTimeToLive(Duration.ofMillis(registeredClientVO.getAccessTokenTimeToLive()))
                        .build());
        return builder.build();
    }

    @PostMapping("save")
    public void save(@RequestBody RegisteredClientSaveVO registeredClientVO) {
        registeredClientRepository.save(convert(registeredClientVO));
    }

    @PostMapping("remove-by-ids")
    public void removeById(@RequestBody List<String> ids) {
        registeredClientService.removeByIds(ids);
    }

    @GetMapping("client-secret/{id}")
    public ResponseEntity<String> clientSecret(@PathVariable String id) {
        RegisteredClient registeredClient = registeredClientRepository.findById(id);
        assert registeredClient != null;
        String clientSecret = registeredClient.getClientSettings().getSetting(LambdaUtil.getFieldName(RegisteredClient::getClientSecret));
        return ResponseEntity.ok(clientSecret);
    }
}
